package miniworld;

import homeMadeExceptions.NetworkException;
import homeMadeExceptions.NotYetImplementedException;
import homeMadeExceptions.ScatterNetException;

import java.util.Vector;

import metric.CentralObservableUnit;
import simulationEngine.SimulationEngine;
import datatypes.BluetoothDeviceAddress;
import datatypes.Host;
import datatypes.LinkMode;
import datatypes.Message;

public class BluezApi {


	private Host host = null;
	
	private boolean initialised = false;
	protected boolean isRFCOMMServer = false;
	private int numberOfSockets = 0;
	protected int maxSlaves = -1;
	
	public BluezApi (Host host) {
		this.host = host;
	}
	
	//Open and initialize HCI device
	protected void hciConfig_up (){
		//Write Metric
		int timeStepID = SimulationEngine.getEngine(host.getSimulationEngineID()).getSimulationEnviroment().getClock().getCurrentTime();
		CentralObservableUnit.enableBluetoothDevice(host, timeStepID);
		//-->call BlueZ APi
		SimulationEngine.getEngine(host.getSimulationEngineID()).getMiniworld().getRoster().setDeviceActive(host.getID());
		initialised = true;
	}
	
	//Close HCI device
	protected void hciConfig_down () {
		//Write Metric
		int tmp = SimulationEngine.getEngine(host.getSimulationEngineID()).getSimulationEnviroment().getClock().getCurrentTime();
		CentralObservableUnit.disableBluetoothDevice(host, tmp);
		//-->call BlueZ APi
		SimulationEngine.getEngine(host.getSimulationEngineID()).getMiniworld().disableDevice(host.getID());
		initialised = false;
	}
	
	//Make Devices visible for other devices and enable Connections
	//short Enable Page und Inquiry Scan
	protected void hciConfig_piscan () {
		//== is not needed, because there's no need to activate an paging and inquiry mode, "paging is everytime on"
	}
	
	//set LinkMode
	protected void hciConfig_setLM (LinkMode LM){
		host.changeLM(LM);
	}
	
	protected void hciTool_inq () {
		//Write Metric
		int tmp = SimulationEngine.getEngine(host.getSimulationEngineID()).getSimulationEnviroment().getClock().getCurrentTime();
		CentralObservableUnit.inquiryScan(host, tmp);
		//-->call Network
		SimulationEngine.getEngine(host.getSimulationEngineID()).getMiniworld().sendInqSignal(host.getBd_Addr());
	}
	
	protected void invokeHciToolAsynchronousInqAnswer (Vector<BluetoothDeviceAddress> answer){
		//Write Metric
		int timeStepID = SimulationEngine.getEngine(host.getSimulationEngineID()).getSimulationEnviroment().getClock().getCurrentTime();
		CentralObservableUnit.inquiryScanAnswer(host, timeStepID);
		try {
			host.getRoutingSocket().onInquiryScanAnswer(answer);
		} catch (NotYetImplementedException e) {
			e.printStackTrace();
		}
	}
	
	//returns local Bluetooth Device Adress 
	protected String hciTool_dev () {
		return host.getBd_Addr().getAddress();
	}
	
	
//--------------Host --> "Network"
	
	/** 
	 * thIs method for intend to get an RFCOMM_SOCKET, is for use, if
	 * you need an Server Socket and if you need an Client Socket too.
	 */
	protected void rfcomm_createSocket () throws NetworkException {
		if (initialised) {
			this.numberOfSockets ++;
		} else {
			//WRITE METRIC THAT SOCKET COULD NOT CREATED, BECAUSE DEVICE IS OFF
			int timeStepID = SimulationEngine.getEngine(host.getSimulationEngineID()).getSimulationEnviroment().getClock().getCurrentTime();
			String cause = "Can't Create Socket! Device isn't enabled.";
			CentralObservableUnit.logError(host, timeStepID, cause);
			//-->throw Exception
			throw new NetworkException(cause);
		}
	}
	
	//Use in RFCOMM CLIENTS
	protected void rfcomm_connect_to_server (BluetoothDeviceAddress server) throws ScatterNetException{
		if ((this.numberOfSockets > 1 && this.isRFCOMMServer)|| (this.numberOfSockets > 0 && !this.isRFCOMMServer)){
			//try to connect
			SimulationEngine.getEngine(host.getSimulationEngineID()).getMiniworld().invokePageDevices(host.getBd_Addr(), server);
		} else {
			throw new ScatterNetException("Cannot connect to Server because, because no slave SocExisting");
		}
	}
	
	//Use in RFCOMM SERVER
	protected void rfcomm_bind_and_listen (int maxSlaves) throws ScatterNetException {
		// allowed connection number listen(i) (should be 7)
		this.maxSlaves = maxSlaves;
		this.isRFCOMMServer = true;
	}
	
	//Called by Routing Protocol
	protected void rfcomm_send(Message msg, BluetoothDeviceAddress target){
		//WRITE METRIC 
		int timeStepID = SimulationEngine.getEngine(host.getSimulationEngineID()).getSimulationEnviroment().getClock().getCurrentTime();
		CentralObservableUnit.networkMsgSend(host, timeStepID, msg, target);
		//-->call BlueZ API
		SimulationEngine.getEngine(host.getSimulationEngineID()).getMiniworld().sendMessage(msg, host.getBd_Addr(), target);
	}

	/**
	 * For get the active Connections of an Host
	 * @return Vector of active Connections
	 */
	public Vector<BluetoothDeviceAddress> rfcomm_show (){
		return SimulationEngine.getEngine(host.getSimulationEngineID()).getMiniworld().invokeRfcommShow(host.getBd_Addr());
	}
	
//--------------NETWORK --> Server	
	
	//Called by Network
	protected void rfcomm_recv(Message msg, BluetoothDeviceAddress source){
		//WRITE METRIC 
		int timeStepID = SimulationEngine.getEngine(host.getSimulationEngineID()).getSimulationEnviroment().getClock().getCurrentTime();
		CentralObservableUnit.networkMessageReceived(host, timeStepID, msg, source);
		//-->call Routing Socket
		host.getRoutingSocket().onReceiveMessage(msg, source);
	}
	
	/**
	 * invoked by Miniworld (indirecly over LocalSimulationQueue)
	 * @param Connected_Remote_Host
	 */
	//send Signal to Server-Host, if Client host connects (upcomming API from Network)
	protected void onClientConnected (BluetoothDeviceAddress Connected_Remote_Host){
		//Write Metric
		int tmp = SimulationEngine.getEngine(host.getSimulationEngineID()).getSimulationEnviroment().getClock().getCurrentTime();
		CentralObservableUnit.routingRemoteHostConnected(host, Connected_Remote_Host,tmp);
		//-->call Routing Socket
		if (this.isRFCOMMServer){
			try {
				host.getRoutingSocket().newRemoteHostConnected(Connected_Remote_Host);
			} catch (NotYetImplementedException e) {
				e.printStackTrace();
			}
		}
	}

//Network --> Socket
	
	/**
	 * invoked by Miniworld
	 * @param target = original source
	 * @param msg
	 */
	protected void invokeMessageLost(BluetoothDeviceAddress target, Message msg ){
		try {
			//WRITE METRIC 
			int timeStepID = SimulationEngine.getEngine(host.getSimulationEngineID()).getSimulationEnviroment().getClock().getCurrentTime();
			CentralObservableUnit.networkMessageLost(host, target, timeStepID, msg);
			//-->call Routing API
			host.getRoutingSocket().invokeMsgLostSignal(target, msg);
		} catch (NotYetImplementedException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * invoked by Miniworld
	 * @param msg
	 * @param target
	 */
	protected void invokeAckSignalForSendedMessage (Message msg, BluetoothDeviceAddress target){
		try {
			//WRITE METRIC 
			int timeStepID = SimulationEngine.getEngine(host.getSimulationEngineID()).getSimulationEnviroment().getClock().getCurrentTime();
			CentralObservableUnit.networkAckSignalInvoked(host, target, timeStepID, msg);
			//-->call Routing API
			host.getRoutingSocket().invokeMsgAckSignal(msg,target);
		} catch (NotYetImplementedException e) {
			e.printStackTrace();
		}
	}
	/**
	 * invoked by Miniworld after the attempt to establish an connection 
	 * @param target -> the remote host, that was tried to connect
	 * @param success -> contains the information if the connection was established successful
	 */
	protected void invokeRfcommConnectReply (BluetoothDeviceAddress target, boolean success){
		try {
			//WRITE METRIC 
			int timeStepID = SimulationEngine.getEngine(host.getSimulationEngineID()).getSimulationEnviroment().getClock().getCurrentTime();
			CentralObservableUnit.networkRfcommConnectReply(host, success, timeStepID);
			//-->call Routing API
			host.getRoutingSocket().rfcommConnectionRequestReply(target, success);
		} catch (NotYetImplementedException e) {
			e.printStackTrace();
		}
	}
}
